export class CanvasTileLayerRenderer {
  constructor() {}

  public prepareFrame(frameState, layerState) {
    const pixelRatio = frameState.pixelRatio;
    const size = frameState.size;
    const viewState = frameState.viewState;
    const projection = viewState.projection;
    const viewResolution = viewState.resolution;
    const viewCenter = viewState.center;

    const tileLayer = this.getLayer();
    const tileSource = /** @type {module:ol/source/Tile} */ tileLayer.getSource();
    const sourceRevision = tileSource.getRevision();
    const tileGrid = tileSource.getTileGridForProjection(projection);
    const z = tileGrid.getZForResolution(viewResolution, this.zDirection);
    const tileResolution = tileGrid.getResolution(z);
    let oversampling = Math.round(viewResolution / tileResolution) || 1;
    let extent = frameState.extent;

    if (layerState.extent !== undefined) {
      extent = getIntersection(extent, layerState.extent);
    }
    if (isEmpty(extent)) {
      // Return false to prevent the rendering of the layer.
      return false;
    }

    const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
    const imageExtent = tileGrid.getTileRangeExtent(z, tileRange);

    const tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);

    /**
     * @type {Object.<number, Object.<string, module:ol/Tile>>}
     */
    const tilesToDrawByZ = {};
    tilesToDrawByZ[z] = {};

    const findLoadedTiles = this.createLoadedTileFinder(
      tileSource,
      projection,
      tilesToDrawByZ
    );

    const hints = frameState.viewHints;
    const animatingOrInteracting =
      hints[ViewHint.ANIMATING] || hints[ViewHint.INTERACTING];

    const tmpExtent = this.tmpExtent;
    const tmpTileRange = this.tmpTileRange_;
    this.newTiles_ = false;
    let tile, x, y;
    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
        if (Date.now() - frameState.time > 16 && animatingOrInteracting) {
          continue;
        }
        tile = this.getTile(z, x, y, pixelRatio, projection);
        if (this.isDrawableTile_(tile)) {
          const uid = getUid(this);
          if (tile.getState() == TileState.LOADED) {
            tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
            const inTransition = tile.inTransition(uid);
            if (
              !this.newTiles_ &&
              (inTransition || this.renderedTiles.indexOf(tile) === -1)
            ) {
              this.newTiles_ = true;
            }
          }
          if (tile.getAlpha(uid, frameState.time) === 1) {
            // don't look for alt tiles if alpha is 1
            continue;
          }
        }

        const childTileRange = tileGrid.getTileCoordChildTileRange(
          tile.tileCoord,
          tmpTileRange,
          tmpExtent
        );
        let covered = false;
        if (childTileRange) {
          covered = findLoadedTiles(z + 1, childTileRange);
        }
        if (!covered) {
          tileGrid.forEachTileCoordParentTileRange(
            tile.tileCoord,
            findLoadedTiles,
            null,
            tmpTileRange,
            tmpExtent
          );
        }
      }
    }

    const renderedResolution =
      ((tileResolution * pixelRatio) / tilePixelRatio) * oversampling;
    if (
      !(
        this.renderedResolution &&
        Date.now() - frameState.time > 16 &&
        animatingOrInteracting
      ) &&
      (this.newTiles_ ||
        !(
          this.renderedExtent_ && containsExtent(this.renderedExtent_, extent)
        ) ||
        this.renderedRevision != sourceRevision ||
        oversampling != this.oversampling_ ||
        (!animatingOrInteracting &&
          renderedResolution != this.renderedResolution))
    ) {
      const context = this.context;
      if (context) {
        const tilePixelSize = tileSource.getTilePixelSize(
          z,
          pixelRatio,
          projection
        );
        const width = Math.round(
          (tileRange.getWidth() * tilePixelSize[0]) / oversampling
        );
        const height = Math.round(
          (tileRange.getHeight() * tilePixelSize[1]) / oversampling
        );
        const canvas = context.canvas;
        if (canvas.width != width || canvas.height != height) {
          this.oversampling_ = oversampling;
          canvas.width = width;
          canvas.height = height;
        } else {
          if (
            this.renderedExtent_ &&
            !equals(imageExtent, this.renderedExtent_)
          ) {
            context.clearRect(0, 0, width, height);
          }
          oversampling = this.oversampling_;
        }
      }

      this.renderedTiles.length = 0;
      /** @type {Array.<number>} */
      const zs = Object.keys(tilesToDrawByZ).map(Number);
      zs.sort(function (a, b) {
        if (a === z) {
          return 1;
        } else if (b === z) {
          return -1;
        } else {
          return a > b ? 1 : a < b ? -1 : 0;
        }
      });
      let currentResolution,
        currentScale,
        currentTilePixelSize,
        currentZ,
        i,
        ii;
      let tileExtent, tileGutter, tilesToDraw, w, h;
      for (i = 0, ii = zs.length; i < ii; ++i) {
        currentZ = zs[i];
        currentTilePixelSize = tileSource.getTilePixelSize(
          currentZ,
          pixelRatio,
          projection
        );
        currentResolution = tileGrid.getResolution(currentZ);
        currentScale = currentResolution / tileResolution;
        tileGutter = tilePixelRatio * tileSource.getGutter(projection);
        tilesToDraw = tilesToDrawByZ[currentZ];
        for (const tileCoordKey in tilesToDraw) {
          tile = tilesToDraw[tileCoordKey];
          tileExtent = tileGrid.getTileCoordExtent(
            tile.getTileCoord(),
            tmpExtent
          );
          x =
            (((tileExtent[0] - imageExtent[0]) / tileResolution) *
              tilePixelRatio) /
            oversampling;
          y =
            (((imageExtent[3] - tileExtent[3]) / tileResolution) *
              tilePixelRatio) /
            oversampling;
          w = (currentTilePixelSize[0] * currentScale) / oversampling;
          h = (currentTilePixelSize[1] * currentScale) / oversampling;
          this.drawTileImage(
            tile,
            frameState,
            layerState,
            x,
            y,
            w,
            h,
            tileGutter,
            z === currentZ
          );
          this.renderedTiles.push(tile);
        }
      }

      this.renderedRevision = sourceRevision;
      this.renderedResolution =
        ((tileResolution * pixelRatio) / tilePixelRatio) * oversampling;
      this.renderedExtent_ = imageExtent;
    }

    const scale = this.renderedResolution / viewResolution;
    const transform = composeTransform(
      this.imageTransform_,
      (pixelRatio * size[0]) / 2,
      (pixelRatio * size[1]) / 2,
      scale,
      scale,
      0,
      ((this.renderedExtent_[0] - viewCenter[0]) / this.renderedResolution) *
        pixelRatio,
      ((viewCenter[1] - this.renderedExtent_[3]) / this.renderedResolution) *
        pixelRatio
    );
    composeTransform(
      this.coordinateToCanvasPixelTransform,
      (pixelRatio * size[0]) / 2 - transform[4],
      (pixelRatio * size[1]) / 2 - transform[5],
      pixelRatio / viewResolution,
      -pixelRatio / viewResolution,
      0,
      -viewCenter[0],
      -viewCenter[1]
    );

    this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
    this.manageTilePyramid(
      frameState,
      tileSource,
      tileGrid,
      pixelRatio,
      projection,
      extent,
      z,
      tileLayer.getPreload()
    );
    this.scheduleExpireCache(frameState, tileSource);

    return this.renderedTiles.length > 0;
  }

  public composeFrame(frameState, layerState){
     
  }
}
